#include "LuaParser.h"
#include <cstring>
#include <globalsink.h>
#include "TokenParser.h"
#include <QDebug>
#include "../luac_parser/core/lauxlib.h"
#include "../luac_parser/core/lua.h"
#include "../luac_parser/core/lparser.h"
#include "../luac_parser/core/lstate.h"
#include "../luac_parser/luablock.h"
#include "../luac_parser/core/llex.h"

LuaParser::LuaParser()
{    
	m_tokenParser = new TokenParser;
    m_root = NULL;
    m_currentBlock = NULL;
    m_data = NULL;
    m_dataLength = 0;
}

LuaParser::~LuaParser()
{
    delete m_tokenParser;
    if(m_root)
        delete m_root;
}


void LuaParser::resetBlocks()
{
    if(m_root)
        delete m_root;
    m_root = NULL;
    m_currentBlock = NULL;
    m_data = NULL;
}

void LuaParser::startBlock(LuaBlockTypeEnum aType, LexState *aState)
{
    if(!aState)
        return;
    if(!m_data)
        return;
    LuaBlock * block = new LuaBlock;
    block->setType(aType);
    //find offset
    const char * position = aState->z->p;
    const char * start = m_data;
    int offset = position - start - aState->buff->n - 1;
    if(aType != ELuaTypeChunk)
    {
        //const char * debug = m_data + offset;
        block->setOffset(offset);
        block->setLength(aState->buff->n);
    }
    else//chunk
    {
        block->setOffset(0);
        block->setLength(m_dataLength);
    }


    if(m_currentBlock)
        m_currentBlock->addBlock(block);
    if(!m_root)
        m_root = block;
    m_currentBlock = block;
}

void LuaParser::endBlock(LexState *aState)
{
    if(!aState)
        return;
    if(!m_currentBlock)
        return;
    if(!m_data)
        return;    
    //set name
    if(m_currentBlock->getType() != ELuaTypeChunk)
    {
        //not needed for other types
        int length = m_currentBlock->getLength();
        char *buffer = (char *)calloc(length + 1, 1);
        memcpy(buffer, m_data + m_currentBlock->getOffset(), length);
        m_currentBlock->setName(buffer);
        free(buffer);
    }

    if(!m_currentBlock->getParent())
        return;
    m_currentBlock = m_currentBlock->getParent();
}

void LuaParser::setCurrentBlockType(LuaBlockTypeEnum aType)
{
    if(!m_currentBlock)
        return;
    m_currentBlock->setType(aType);
}


void LuaParser::parseData(QString aData)
{
    QString dataWithLastNewLine = aData + '\n';
    QByteArray temp = dataWithLastNewLine.toLocal8Bit();
    const char * content = temp.constData();
    m_dataLength = strlen(content);
    //creating global scope
	m_tokenParser->parseData(content);
    //processShortStrings();
    //processShortComments();
    //processLongComments();
    //processLongStrings();
    resetBlocks();
    setParser(this);
    //actual parsing via Lua core
    lua_State * L  = luaL_newstate();
    m_data = temp.constData();
    luaL_loadbufferx(L, temp.constData(), m_dataLength, "lua", "bt");
    lua_close(L);   
    //m_root->print(0);
    collectGlobalFunctions(m_root);
    m_localFunctionsCache.clear();
    collectLocalFunctions(m_root);
    collectGlobalReferences();
}




TokenParser *LuaParser::getTokenParser()
{
    return m_tokenParser;
}

//concatenate everything with dots
QString readBlockName(LuaBlock * aBlock)
{
    QVector<LuaBlock *> blocks = aBlock->getBlocks();
    int length = blocks.size();
    QString name = "";
    for(int i = 0; i < length; i++)
    {
        if(blocks[i]->getName().length() > 0)
        {            
            name = name + blocks[i]->getName();
            if(i < length - 1)
                name = name + ".";
        }
    }
    return name;
}


QString readParameters(LuaBlock * aBlock)
{
    QVector<LuaBlock *> blocks = aBlock->getBlocks();
    int length = blocks.size();
    QString name = "(";
    for(int i = 0; i < length; i++)
    {
        if(blocks[i]->getName().length() > 0)
        {
            name = name + blocks[i]->getName();
            if(i < length - 1)
                name = name + ",";
        }
    }
    name = name + ")";
    return name;
}

void LuaParser::cacheGlobalFunction(QString aName, int aOffset)
{
    //qDebug() << "Caching function: " << aName;
    if(m_globalFunctionsCache.contains(aName))
        return;
    m_globalFunctionsCache[aName] = aOffset;
}

void LuaParser::cacheGlobal(QString aName, int aOffset)
{
    //qDebug() << "Caching function: " << aName;
    if(m_globalsCache.contains(aName))
        return;
    m_globalsCache[aName] = aOffset;
}

int LuaParser::getGlobalReference(QString aWord)
{
    if(m_globalsCache.contains(aWord))
        return m_globalsCache[aWord];
    return REFERENCE_NOT_FOUND;
}

QString  LuaParser::readFunctionName(LuaBlock * aFunction, int & aOffset, bool aIncludeParams,
                                     QString & aNameWithoutParameters)
{
    QVector<LuaBlock *> blocks = aFunction->getBlocks();
    int length = blocks.size();
    QString functionName;
    QString parameters = "()";
    aOffset = aFunction->getOffset();
    for(int i = 0; i < length; i++)
    {
        if(blocks[i]->getType() == ELuaTypeFunctionName)
        {
            functionName = readBlockName(blocks[i]);
            aOffset = blocks[i]->getOffset();
        }
        else if(blocks[i]->getType() == ELuaTypeParameters)
            parameters = readParameters(blocks[i]);
    }

    if(functionName.length() > 0)
    {
        aNameWithoutParameters = functionName;
        if(aIncludeParams)
            return functionName + parameters;
        else
            return functionName;
    }

    return "";
}

void LuaParser::collectGlobalFunctions(LuaBlock * aRoot)
{
    m_globalFunctionsCache.clear();
    m_globalsToFunctionsMap.clear();
    //search global functions
    //they should be declared in the main chunk
    QVector<LuaBlock *> blocks = aRoot->getBlocks();
    int length = blocks.size();
    for(int i = 0; i < length; i++)
    {
        if(blocks[i]->getType() == ELuaTypeFunction)
        {            
            int offset = blocks[i]->getOffset();
            QString nameWithoutParameters;
            QString name = readFunctionName(blocks[i], offset, true, nameWithoutParameters);
            if(name.length() > 0)
            {
                m_globalsToFunctionsMap[nameWithoutParameters] = name;
                cacheGlobalFunction(name, offset);
            }
        }        
    }
}


void LuaParser::collectLocalFunctions(LuaBlock * aRoot)
{
    QVector<LuaBlock *> blocks = aRoot->getBlocks();
    int length = blocks.size();
    for(int i = 0; i < length; i++)
    {
        if(blocks[i]->getType() == ELuaTypeLocalFunction)
        {
            QVector<LuaBlock *> subBlocks = blocks[i]->getBlocks();
            QString name;
            QString parameters = "()";
            int offset = blocks[i]->getOffset();
            if(subBlocks.size() > 0)
            {
                LuaBlock * nameBlock = subBlocks[0];
                if(nameBlock->getType() == ELuaTypeLocalVariable)
                {
                    offset = nameBlock->getOffset();
                    name = nameBlock->getName();

                }

            }
            if(subBlocks.size() > 1)
            {
                LuaBlock * possibleParams = subBlocks[1];
                if(possibleParams->getType() == ELuaTypeParameters)
                    parameters = readParameters(possibleParams);
            }
            if(name.length() > 0)
            {
                QString functionName = name + parameters;
                if(!m_localFunctionsCache.contains(functionName))
                    m_localFunctionsCache[functionName] = offset;
            }

        }
        collectLocalFunctions(blocks[i]);
    }
}

LuaBlock *LuaParser::getEnclosingBlock(int aOffset)
{
    if(!m_root)
        return NULL;
    return m_root->getEnclosingBlock(aOffset);
}




int LuaParser::getLocalReference(QString aWord, int aOffset)
{
    LuaBlock * enclosingBlock = getEnclosingBlock(aOffset);
    if(!enclosingBlock)
        return REFERENCE_NOT_FOUND;
    //get hierarchy first
    QVector<LuaBlock *> hierarchy;
    LuaBlock * current = enclosingBlock;
    bool infiniteLoopCheck = 0;
    while(current)
    {
        infiniteLoopCheck++;
        if(infiniteLoopCheck > 1000)
        {
            qDebug() << "getLocalReference: Infinite loop!";
            break;
        }
        if(current->getParent())
            hierarchy.append(current->getParent());
        current = current->getParent();
    }
    for(int i = 0; i < hierarchy.size(); i++)
    {
        LuaBlock * block = hierarchy[i];
        int offset = 0;
        if(i == 0)
            offset = getLocalReferenceFromBlock(block, aWord, NULL);
        else
            offset = getLocalReferenceFromBlock(block, aWord, hierarchy[i - 1]);
        if(offset != REFERENCE_NOT_FOUND)
            return offset;

    }
    return REFERENCE_NOT_FOUND;
}

//returns offset
int LuaParser::findMatchingLocalVariableDeclaration(LuaBlock * aBlock, QString aMatch)
{
    QVector<LuaBlock *> blocks = aBlock->getBlocks();
    int length = blocks.size();
    for(int i = 0; i < length; i++)
    {
        if(blocks[i]->getType() == ELuaTypeLocalVariable)
        {
            if(blocks[i]->getName() == aMatch)
                return blocks[i]->getOffset();
        }
    }
    return REFERENCE_NOT_FOUND;
}


QString readPrimaryExpression(LuaBlock * aBlock)
{
    QVector<LuaBlock *> blocks = aBlock->getBlocks();
    int length = blocks.size();
    QString name;
    for(int i = 0; i < length; i++)
    {
        if(blocks[i]->getType() != ELuaTypeGlobalVariable && blocks[i]->getType() != ELuaTypeFieldSelector)
            break;
        if(blocks[i]->getName().length() > 0)
        {
            name = name + blocks[i]->getName();
            if(i < length - 1)
                name = name + ".";
        }
    }
    return name;
}

void LuaParser::collectGlobalReferences()
{
    m_globalsCache.clear();
    if(!m_root)
        return;
    QVector<LuaBlock *> blocks = m_root->getBlocks();
    int length = blocks.size();
    for(int i = 0; i < length; i++)
    {
        //we need to collect global functions and assignments to global variables(declarations)
        if(blocks[i]->getType() == ELuaTypeFunction)
        {
            int offset = blocks[i]->getOffset();
            QString temp;
            QString name = readFunctionName(blocks[i], offset, false, temp);
            if(name.length() > 0)
                cacheGlobal(name, offset);
        }
        else if(blocks[i]->getType() == ELuaTypeAssignment)
        {
            QVector<LuaBlock *> assignmentBlocks = blocks[i]->getBlocks();
            if(assignmentBlocks.size() > 0)
            {
                LuaBlock * potentialGlobal = assignmentBlocks[0];
                if(potentialGlobal->getType() == ELuaTypeGlobalVariable
                        && potentialGlobal->getName().length() > 0)
                    cacheGlobal(potentialGlobal->getName(), potentialGlobal->getOffset());
                else if(potentialGlobal->getType() == ELuaTypePrimaryExpression)
                {
                    QString name = readPrimaryExpression(potentialGlobal);
                    if(name.length() > 0)
                        cacheGlobal(name, potentialGlobal->getOffset());
                }
            }
        }
    }
}

//returns offset
int LuaParser::findMatchingLocalFunctionDeclaration(LuaBlock * aBlock, QString aMatch)
{
    QVector<LuaBlock *> blocks = aBlock->getBlocks();
    int length = blocks.size();
    if(length == 0)
        return REFERENCE_NOT_FOUND;
    if(!blocks[0]->getType() == ELuaTypeLocalVariable)
        return REFERENCE_NOT_FOUND;

   if(blocks[0]->getName() == aMatch)
        return blocks[0]->getOffset();

    return REFERENCE_NOT_FOUND;
}


int LuaParser::getLocalReferenceFromBlock(LuaBlock *aBlock, QString aWord, LuaBlock *aStopAtBlock)
{
    QVector<LuaBlock *> blocks = aBlock->getBlocks();
    int length = blocks.size();
    for(int i = 0; i < length; i++)
    {
        if(blocks[i] == aStopAtBlock)
            return REFERENCE_NOT_FOUND;
        //we need to check local variable declarations and local functions
        if(blocks[i]->getType() == ELuaTypeLocalVariableDeclaration
                || blocks[i]->getType() == ELuaTypeParameters)
        {
            int offset = findMatchingLocalVariableDeclaration(blocks[i], aWord);
            if(offset != REFERENCE_NOT_FOUND)
                return offset;
        }
        if(blocks[i]->getType() == ELuaTypeLocalFunction)
        {
            int offset = findMatchingLocalFunctionDeclaration(blocks[i], aWord);
            if(offset != REFERENCE_NOT_FOUND)
                return offset;
        }
    }

    return REFERENCE_NOT_FOUND;
}


void LuaParser::getLocalCompletions(QString aPrefix, int aOffset, QStringList & aSink)
{
    LuaBlock * enclosingBlock = getEnclosingBlock(aOffset);
    if(!enclosingBlock)
        return;
    //get hierarchy first
    QVector<LuaBlock *> hierarchy;
    LuaBlock * current = enclosingBlock;
    bool infiniteLoopCheck = 0;
    while(current)
    {
        infiniteLoopCheck++;
        if(infiniteLoopCheck > 1000)
        {
            qDebug() << "getLocalCompletions: Infinite loop!";
            break;
        }
        if(current->getParent())
            hierarchy.append(current->getParent());
        current = current->getParent();
    }
    hierarchy.append(m_root);
    for(int i = 0; i < hierarchy.size(); i++)
    {
        LuaBlock * block = hierarchy[i];
        if(i == 0)
            getLocalCompletionsFromBlock(block, aPrefix, NULL, aSink);
        else
             getLocalCompletionsFromBlock(block, aPrefix, hierarchy[i - 1], aSink);
    }
}

bool isValidPrefix(QString aPrefix, QString aSource)
{
    if(QString::compare(aPrefix, aSource, Qt::CaseInsensitive) == 0)
        return true;
    if(aSource.startsWith(aPrefix, Qt::CaseInsensitive))
        return true;
    return false;
}

void LuaParser::getLocalCompletionsFromBlock(LuaBlock *aBlock, QString aPrefix, LuaBlock *aStopAtBlock, QStringList & aSink)
{
    QVector<LuaBlock *> blocks = aBlock->getBlocks();
    int length = blocks.size();
    for(int i = 0; i < length; i++)
    {
        if(blocks[i] == aStopAtBlock)
            return;
        //we need to check local variable declarations and local functions
        if(blocks[i]->getType() == ELuaTypeLocalVariableDeclaration
                || blocks[i]->getType() == ELuaTypeParameters)
        {
            collectAllMatchingLocalVariableDeclarations(blocks[i], aPrefix, aSink);
        }
        if(blocks[i]->getType() == ELuaTypeLocalFunction)
        {
            QString name = readMatchingLocalFunctionDeclaration(blocks[i]);
            if(isValidPrefix(aPrefix, name))
                aSink.append(name);
        }
    }
}

//returns offset
void LuaParser::collectAllMatchingLocalVariableDeclarations(LuaBlock * aBlock, QString aPrefix, QStringList & aSink)
{
    QVector<LuaBlock *> blocks = aBlock->getBlocks();
    int length = blocks.size();
    for(int i = 0; i < length; i++)
    {
        if(blocks[i]->getType() == ELuaTypeLocalVariable)
        {
            QString name = blocks[i]->getName();
            if(isValidPrefix(aPrefix, name))
                aSink.append(name);
        }
    }
}

QString LuaParser::readMatchingLocalFunctionDeclaration(LuaBlock * aBlock)
{
    QVector<LuaBlock *> blocks = aBlock->getBlocks();
    int length = blocks.size();
    if(length == 0)
        return "";
    if(!blocks[0]->getType() == ELuaTypeLocalVariable)
        return "";
    return blocks[0]->getName();
}


void LuaParser::getGlobalCompletions(QString aPrefix, QStringList & aSink)
{
    QList<QString> keys = m_globalsCache.keys();
    foreach(QString key, keys)
    {
        if(isValidPrefix(aPrefix, key))
        {
            if(m_globalsToFunctionsMap.contains(key))
                aSink.append(m_globalsToFunctionsMap[key]);
            else
                aSink.append(key);
        }

    }
}


void LuaParser::getAllGlobalReferences(QString aPrefix,
                                       QStringList & aTextSink,
                                       QStringList & aLinkSink,
                                       QString aFile)
{
    QList<QString> keys = m_globalsCache.keys();
    foreach(QString key, keys)
    {
        if(isValidPrefix(aPrefix, key))
        {
            int offset = m_globalsCache[key];
            QString link = "global:" + QString::number(offset)+ ":" + aFile + ":" + key;
            if(m_globalsToFunctionsMap.contains(key))
                aTextSink.append(m_globalsToFunctionsMap[key]);
            else
                aTextSink.append(key);
            aLinkSink.append(link);
        }

    }
}
